import json
import logging
from typing import Tuple, Optional
from cryptography.hazmat.primitives.hashes import SHA1, Hash
import os

logging.getLogger().disabled = True
logging.basicConfig(level=logging.DEBUG)

logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)
logger.propagate = False
log_fmt = logging.Formatter('%(asctime)s %(name)s %(levelname)-8s %(message)s')
log_console = logging.StreamHandler()
log_console.setFormatter(log_fmt)
logger.addHandler(log_console)

ca_keys = {}
ca_key_file = os.path.abspath(os.path.join(
    os.path.abspath(__file__), '..', 'PBOC_CA_PUB_KEYS.json'))
with open(ca_key_file, 'r') as f:
    ca_keys.update(json.load(f))


def crypto_int_from_octets(octets: bytes) -> Tuple[int, int]:
    num = int.from_bytes(octets, byteorder='big', signed=False)
    return num, len(octets)


def crypto_int_from_hex(hex_str: str) -> Tuple[int, int]:
    """将密钥的hex字符串转化为密码学运算需要的整数并保留其长度"""
    octets = bytes.fromhex(hex_str)
    return crypto_int_from_octets(octets)


def rsa_restore(secret: bytes, n: int, e: int, n_len: int) -> bytes:
    assert len(secret) == n_len
    secret_num, _ = crypto_int_from_octets(secret)
    plain_num = secret_num ** e % n
    return plain_num.to_bytes(length=n_len, byteorder='big', signed=False)


def do_sha1(plain: bytes):
    h = Hash(SHA1())
    h.update(plain)
    return h.finalize()


def restore_issuer_cert(tag_8f, tag_90, tag_9f32, tag_92):
    """恢复发卡行公钥证书

    JR/T 0025.7-2018 5.3.3.3 (P14)

    tag_8f: CA公钥索引
    tag_90: 发卡行公钥证书（待恢复）
    tag_9f32: 发卡行RSA公钥指数
    tag_92: 发卡行RSA公钥余数
    """
    logger.debug("========恢复发卡行公钥证书========")

    ca_key = ca_keys[tag_8f.hex()]
    ca_n, n_ca = crypto_int_from_hex(ca_key['key_data_n'])
    ca_e, _ = crypto_int_from_hex(ca_key['key_data_e'])
    if len(tag_90) != n_ca:
        logger.error("发卡行证书数据长度与CA公钥模长不一致")
        return None, None

    # 恢复发卡行密钥和证书
    issuer_cert = rsa_restore(tag_90, ca_n, ca_e, n_ca)
    # JR/T 0025.7-2018 5.4.3.3 表28 (P27)
    struct = {
        '恢复数据头': issuer_cert[0:1],
        '证书格式': issuer_cert[1:2],
        '发卡行标识': issuer_cert[2:6],
        '证书失效日期': issuer_cert[6:8],
        '证书序列号': issuer_cert[8:11],
        '杂凑算法标识': issuer_cert[11:12],
        '发卡行公钥算法标识': issuer_cert[12:13],
        '发卡行公钥长度': issuer_cert[13:14],
        '发卡行公钥指数长度': issuer_cert[14:15],
        '发卡行公钥或发卡行公钥的最左边字节': issuer_cert[15:-21],
        '杂凑结果': issuer_cert[-21:-1],
        '恢复数据结尾': issuer_cert[-1:]
    }

    # TODO: 返回格式用于检验

    for k, v in struct.items():
        logger.debug("{}: {}".format(k, v.hex()))

    n_i = issuer_cert[13]
    if n_i + 36 <= n_ca:
        assert issuer_cert[(15 + n_i):-21] == b'\xbb' * (n_ca - n_i - 36)
        assert tag_92 is None
        issuer_cert_n_octets = issuer_cert[15: n_i + 15]
        data_to_hash = issuer_cert[1:-21] + tag_9f32
    else:
        assert tag_92 is not None
        assert n_i + 36 == n_ca + len(tag_92)
        issuer_cert_n_octets = issuer_cert[15:-21] + tag_92
        data_to_hash = issuer_cert[1:-21] + issuer_cert_n_octets + tag_9f32
    logger.debug("待计算的链接数据：{}".format(data_to_hash.hex()))

    issuer_cert_hash = do_sha1(data_to_hash)
    logger.debug("计算出的杂凑值：{}".format(issuer_cert_hash.hex(' ')))
    logger.debug("恢复出的杂凑值：{}".format(issuer_cert[-21:-1].hex(' ')))

    if issuer_cert_hash == issuer_cert[-21:-1]:
        logger.info("========发卡行公钥证书验证通过========")
        return issuer_cert_n_octets, struct
    else:
        logger.critical("========发卡行公钥证书验证失败========")
        return None, struct

def restore_iccard_cert(issuer_cert_n_octets, tag_9f32, tag_9f46, tag_9f48, tag_9f47, sda_octets):
    """恢复IC卡公钥证书

    # JR/T 0025.7-2018 5.4.3.1 (P25)

    issuer_cert_octets: 发卡行RSA公钥数据（模）
    tag_9f32: 发卡行RSA公钥指数
    tag_9f46: IC卡公钥证书（待恢复）
    tag_9f48: IC卡RSA公钥余数
    tag_9f47: IC卡RSA公钥指数
    """
    logger.debug("========恢复IC卡公钥证书========")
    issuer_cert_n, n_i = crypto_int_from_octets(issuer_cert_n_octets)
    issuer_cert_e, _ = crypto_int_from_octets(tag_9f32)
    if len(tag_9f46) != n_i:
        logger.error("IC卡证书数据长度与发卡行证书公钥模长不一致")
        return None, None

    iccard_cert = rsa_restore(tag_9f46, issuer_cert_n, issuer_cert_e, n_i)

    struct = {
        '恢复数据头': iccard_cert[0:1],
        '证书格式': iccard_cert[1:2],
        '应用主账号': iccard_cert[2:12],
        '证书失效日期': iccard_cert[12:14],
        '证书序列号': iccard_cert[14:17],
        '杂凑算法标识': iccard_cert[17:18],
        'IC卡公钥算法标识': iccard_cert[18:19],
        'IC卡公钥长度': iccard_cert[19:20],
        'IC卡公钥指数长度': iccard_cert[20:21],
        'IC卡公钥或发卡行公钥的最左边字节': iccard_cert[21:-21],
        '杂凑结果': iccard_cert[-21:-1],
        '恢复数据结尾': iccard_cert[-1:]
    }
    for k, v in struct.items():
        logger.debug("{}: {}".format(k, v.hex()))

    n_ic = iccard_cert[19]

    buffer = bytearray()
    if n_ic + 42 <= n_i:
        assert iccard_cert[(21 + n_i):-21] == b'\xbb' * (n_i - n_i - 36)
        assert tag_9f48 is None
        iccard_cert_n_octets = iccard_cert[21: n_ic + 21]
        buffer.extend(iccard_cert[1:-21])
        buffer.extend(tag_9f47)
        # data_to_hash = iccard_cert[1:-21] + tag_9f47
    else:
        assert tag_9f48 is not None
        assert n_ic + 42 == n_i + len(tag_9f48)
        iccard_cert_n_octets = iccard_cert[21:-21] + tag_9f48
        buffer.extend(iccard_cert[1:21])
        buffer.extend(iccard_cert_n_octets)
        buffer.extend(tag_9f47)

        # data_to_hash = iccard_cert[1:21] + iccard_cert_n_octets + tag_9f47

    buffer.extend(sda_octets)
    logger.debug("待计算的链接数据：({} bytes) {}".format(len(buffer), buffer.hex()))
    iccard_cert_hash = do_sha1(buffer)
    logger.debug("计算出的杂凑值：{}".format(iccard_cert_hash.hex(' ')))
    logger.debug("恢复出的杂凑值：{}".format(iccard_cert[-21:-1].hex(' ')))
    if iccard_cert_hash == iccard_cert[-21:-1]:
        logger.info("========IC卡公钥证书验证通过========")
        return iccard_cert_n_octets, struct
    else:
        logger.critical("========IC卡公钥证书验证失败========")
        return None, struct


def _check_hash(buffer: bytes, sdapp_data: bytes):
    logger.debug("待计算的链接数据：({} bytes) {}".format(len(buffer), buffer.hex()))
    hashed = do_sha1(bytes(buffer))
    logger.debug("计算出的杂凑值：{}".format(hashed.hex(' ')))
    logger.debug("恢复出的杂凑值：{}".format(sdapp_data[-21:-1].hex(' ')))
    if hashed == sdapp_data[-21:-1]:
        return True
    else:
        return False

def do_sda(issuer_cert_n_octets: bytes, tag_9f32: bytes, tag_93: bytes, sda_octets: bytes) -> Optional[dict]:
    """执行静态数据认证（SDA）

    JR/T 0025.7-2018 5.3.3.4 (P15)

    issuer_cert_octets: 发卡行RSA公钥数据（模）
    tag_9f32: 发卡行RSA公钥指数
    tag_93: 签名的静态应用数据
    sda_octets: 参与验证的静态数据
    """
    issuer_cert_n, n_i = crypto_int_from_octets(issuer_cert_n_octets)
    issuer_cert_e, _ = crypto_int_from_octets(tag_9f32)
    static_app_data = rsa_restore(tag_93, issuer_cert_n, issuer_cert_e, n_i)
    struct = {
        '恢复数据头': static_app_data[0:1],
        '签名数据格式': static_app_data[1:2],
        '杂凑算法标识': static_app_data[2:3],
        '数据验证代码': static_app_data[3:5],
        '填充字节': static_app_data[5:-21],
        '杂凑结果': static_app_data[-21:-1],
        '恢复数据结尾': static_app_data[-1:]
    }

    for k, v in struct.items():
        logger.debug("{}: {}".format(k, v.hex()))

    buffer = bytearray(static_app_data[1:-21])
    buffer.extend(sda_octets)
    if _check_hash(buffer, static_app_data):
        logger.info("========SDA 验证通过========")
        return struct
    else:
        logger.critical("========SDA 验证失败========")
        return None


def _restore_dda_struct(iccard_cert_n_octets: bytes, iccard_cert_e_octets: bytes, signed_dad):
    iccard_cert_n, n_ic = crypto_int_from_octets(iccard_cert_n_octets)
    iccard_cert_e, _ = crypto_int_from_octets(iccard_cert_e_octets)
    dynamic_app_data = rsa_restore(signed_dad, iccard_cert_n, iccard_cert_e, n_ic)
    l_dd = dynamic_app_data[3]
    struct = {
        '恢复数据头': dynamic_app_data[0:1],
        '签名数据格式': dynamic_app_data[1:2],
        '杂凑算法标识': dynamic_app_data[2:3],
        'IC卡动态数据长度': dynamic_app_data[3:4],
        'IC卡动态数据': dynamic_app_data[4:l_dd + 4],
        '填充字节': dynamic_app_data[l_dd + 4:-21],
        '杂凑结果': dynamic_app_data[-21:-1],
        '恢复数据结尾': dynamic_app_data[-1:]
    }
    for k, v in struct.items():
        logger.debug("{}: {}".format(k, v.hex()))

    return dynamic_app_data, struct


def do_dda(iccard_cert_n_octets: bytes, iccard_cert_e_octets: bytes, int_auth_ddol: bytes, int_auth_resp: bytes) -> Optional[dict]:
    """执行标准动态数据认证（DDA)

    JR/T 0025.7-2018 5.4.3.5 (P28)

    iccard_cert_n_octets: IC卡RSA公钥数据（模）
    iccard_cert_e_octets: IC卡RSA公钥指数，标签9F47
    int_auth_ddol: 内部认证命令（INTERNAL AUTHENTICATE）的DDOL
    int_auth_resp: 内部认证命令的响应数据（动态签名数据）
    """
    #
    logger.debug("========标准动态数据验证DDA========")
    dynamic_app_data, struct = _restore_dda_struct(iccard_cert_n_octets, iccard_cert_e_octets, int_auth_resp)

    buffer = bytearray(dynamic_app_data[1:-21])
    buffer.extend(int_auth_ddol)

    if _check_hash(buffer, dynamic_app_data):
        logger.info("========标准动态数据（DDA）验证通过========")
        return struct
    else:
        logger.critical("========标准动态数据（DDA）验证失败========")
        return None


def do_fdda(iccard_cert_n_octets: bytes, iccard_cert_e_octets: bytes, tag_9f4b: bytes,
            tag_9f37: bytes, tag_9f02: bytes, tag_5f2a: bytes, tag_9f69: bytes,
            version = 0x01):
    """执行快速动态数据认证（fDDA）

    JR/T 0025.12-2018 B.2 (P46)
    JR/T 0025.5-2018 5.4.3.5 (P29)

    iccard_cert_n_octets: IC卡RSA公钥数据（模）
    iccard_cert_e_octets: IC卡RSA公钥指数(9F47)
    tag_9f4b: 签名的动态应用数据
    tag_9f37: 不可预知数（随机数）
    tag_9f02: 授权金额
    tag_5f2a: 交易货币代码
    tag_9f69: 卡片认证相关数据
    """

    logger.debug("========快速动态数据验证fDDA========")
    dynamic_app_data, struct = _restore_dda_struct(iccard_cert_n_octets, iccard_cert_e_octets, tag_9f4b)
    buffer = bytearray(dynamic_app_data[1:-21])
    assert version == 0x00 or version == 0x01
    buffer.extend(tag_9f37)
    if version == 0x01:
        buffer.extend(tag_9f02)
        buffer.extend(tag_5f2a)
        buffer.extend(tag_9f69)

    if _check_hash(buffer, dynamic_app_data):
        logger.info("========快速动态数据（fDDA）验证通过========")
        return struct
    else:
        logger.critical("========快速动态数据（fDDA）验证失败========")
        return None